/*
 *  linux/kernel/tty_io.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
 * or rs-channels. It also implements echoing, cooked mode etc.
 *
 * Kill-line thanks to John T Kohl.
 */
#include <ctype.h>
#include <errno.h>
#include <signal.h>

// 下面给出一些信号在信号位图中对应的比特屏蔽位
#define ALRMMASK (1<<(SIGALRM-1)) 	// 警告（alarm）信号屏蔽位
#define KILLMASK (1<<(SIGKILL-1))	// 终止（kill） 信号屏蔽位
#define INTMASK (1<<(SIGINT-1))		// 键盘终止（int)信号屏蔽位
#define QUITMASK (1<<(SIGQUIT-1))	// 键盘退出 (quit)信号屏蔽位
#define TSTPMASK (1<<(SIGTSTP-1))	// tty 发出的停止进程（tty stop） 信号屏蔽位

#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/system.h>

// 获取 termios 结构中三个模式标志集之一，或者用于判断一个标志集是否有置位标志
#define _L_FLAG(tty,f)	((tty)->termios.c_lflag & f)	// 本地模式标志
#define _I_FLAG(tty,f)	((tty)->termios.c_iflag & f)	// 输入模式标志
#define _O_FLAG(tty,f)	((tty)->termios.c_oflag & f)	// 输出模式标志

// 取 termios 结构终端特殊（本地）模式标志集中的一个标志
#define L_CANON(tty)	_L_FLAG((tty),ICANON)	// 取规范模式标志	
#define L_ISIG(tty)		_L_FLAG((tty),ISIG)		// 取信号标志
#define L_ECHO(tty)		_L_FLAG((tty),ECHO)		// 取回显字符标志
#define L_ECHOE(tty)	_L_FLAG((tty),ECHOE)	// 规范模式时取回显擦出标志
#define L_ECHOK(tty)	_L_FLAG((tty),ECHOK)	// 规范模式时取KILL擦除当前行标志
#define L_ECHOCTL(tty)	_L_FLAG((tty),ECHOCTL)	// 取回显控制字符标志
#define L_ECHOKE(tty)	_L_FLAG((tty),ECHOKE)	// 规范模式时取KILL擦除行并回显标志

// 取 termios 结构输入模式标志集中的一个标志
#define I_UCLC(tty)	_I_FLAG((tty),IUCLC)	// 取大写到小写转换标志
#define I_NLCR(tty)	_I_FLAG((tty),INLCR)	// 取换行符NL转回车符CR标志
#define I_CRNL(tty)	_I_FLAG((tty),ICRNL)	// 取回车符CR转换行符NL标志
#define I_NOCR(tty)	_I_FLAG((tty),IGNCR)	// 取忽略回车符CR标志

// 取 termios 结构输出模式标志集中的一个标志
#define O_POST(tty)	_O_FLAG((tty),OPOST)		// 取执行输出处理标志（后处理）
#define O_NLCR(tty)	_O_FLAG((tty),ONLCR)		// 取换行符NL转回车换行符 CR-NL 标志
#define O_CRNL(tty)	_O_FLAG((tty),OCRNL)		// 取回车符CR转换行符NL标志
#define O_NLRET(tty)	_O_FLAG((tty),ONLRET)	// 取换行符NL执行回车功能的标志
#define O_LCUC(tty)	_O_FLAG((tty),OLCUC)		// 取小写转大写字符标志

// tty 数据结构的 tty_table 数组。其中包含三个初始化项数据，分别是控制台、串口终端1和串口终端2的初始化数据。
struct tty_struct tty_table[] = {
	{
		{ICRNL,		/* change incoming CR to NL */
		OPOST|ONLCR,	/* change outgoing NL to CRNL */
		0,
		ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,
		0,		/* console termio */
		INIT_C_CC},
		0,			/* initial pgrp */
		0,			/* initial stopped */
		con_write,
		{0,0,0,0,""},		/* console read-queue */
		{0,0,0,0,""},		/* console write-queue */
		{0,0,0,0,""}		/* console secondary queue */
	},{
		{0, /* no translation */
		0,  /* no translation */
		B2400 | CS8,
		0,
		0,
		INIT_C_CC},
		0,
		0,
		rs_write,
		{0x3f8,0,0,0,""},		/* rs 1 */
		{0x3f8,0,0,0,""},
		{0,0,0,0,""}
	},{
		{0, /* no translation */
		0,  /* no translation */
		B2400 | CS8,
		0,
		0,
		INIT_C_CC},
		0,
		0,
		rs_write,
		{0x2f8,0,0,0,""},		/* rs 2 */
		{0x2f8,0,0,0,""},
		{0,0,0,0,""}
	}
};

/*
 * these are the tables used by the machine code handlers.
 * you can implement pseudo-tty's or something by changing
 * them. Currently not done.
 */
struct tty_queue * table_list[]={
	&tty_table[0].read_q, &tty_table[0].write_q, // 控制台终端读、写缓冲队列地址。
	&tty_table[1].read_q, &tty_table[1].write_q, // 串行口1 终端读、写缓冲队列地址，
	&tty_table[2].read_q, &tty_table[2].write_q  // 串行口2 终端读、写缓冲队列地址，
	};

// 初始化 tty 终端
void tty_init(void)
{
	rs_init();		// 初始化串行中断程序和串行接口1和2
	con_init();		// 初始化控制台终端
}

// tty 键盘中断字符（^C、^\等）处理函数
// 实现：向tty结构中指明的（前台）进程组中所有进程发送指定信号 mask，通常该信号是 SIGINT 、SIGQUIT
// 参数：tty — 指定终端的 tty 结构指针；mask - 信号屏蔽位。
// 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出
void tty_intr(struct tty_struct * tty, int mask)
{
	int i;

	if (tty->pgrp <= 0)
		return;
	for (i=0;i<NR_TASKS;i++)
		if (task[i] && task[i]->pgrp==tty->pgrp)
			task[i]->signal |= mask;
}

// 如果队列缓冲区空，则让进程进入可中断睡眠状态。
// 参数：queue - 指定队列的指针。
// 进程在取队列缓冲区中字符之前需要调用此函数加以验证。
static void sleep_if_empty(struct tty_queue * queue)
{
	cli();
	// 若当前进程没有信号要处理（进程信号位图为空），并且指定的队列缓冲区空，则让进程进入可中断睡眠状态。（直到队列非空退出循环）
	while (!current->signal && EMPTY(*queue))
		interruptible_sleep_on(&queue->proc_list);
	sti();
}

// 若队列缓冲区满，则让进程进入可中断的睡眠状态。
// 参数：queue -指定队列的指针。
// 进程在往队列缓冲区中写入字符之前需要调用此函数判断队列情况
static void sleep_if_full(struct tty_queue * queue)
{
	// 如果队列缓冲区不满则返回退出。
	if (!FULL(*queue))
		return;
	cli();
	// 若进程没有信号需要处理，并且队列缓冲区中空闲剩余区长度<128，则让进程进入可中断睡眠状态。
	while (!current->signal && LEFT(*queue)<128)
		interruptible_sleep_on(&queue->proc_list);
	sti();
}

// 等待按键。
// 实现：如果控制台读队列缓冲区secondary空，则让进程进入可中断睡眠状态
void wait_for_keypress(void)
{
	sleep_if_empty(&tty_table[0].secondary);
}

// 复制成 规范模式 字符序列。
// 实现：根据终端 termios 结构中设置的各种标志，将指定 tty 终端读队列缓冲区 read_q 中的字符复制转换成
// 		规范模式（熟模式〉字符，并存放在辅助队列（规范模式队列secondary）中。如果开启回显，则同时将字符写入write_q队列中。
// 参数：tty一指定终端的 tty 结构指针。
void copy_to_cooked(struct tty_struct * tty)
{
	signed char c;

	/*
	具体实现：
	如果 tty 的读队列缓冲区不空并且辅助队列缓冲区不满，则循环读取读队列缓冲区中的字符， 
	转换成规范模式后放入辅助队列（secondary）缓冲区中，直到读队列缓冲区空或者辅助队列满为止。

	在循环体内，程序首先从读队列缓冲区尾指针处取一字符，并把尾指针前移一个字符位置。
	然后根据终端 termios 中输入模式标志集中设置的标志对字符进行处理。
	*/
	while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {
		GETCH(tty->read_q,c);  // 从read_q中取一个字符，赋值到c中
		/*
		如果该字符是回车符 CR（13），那么：
		*/
		if (c==13)
			// 若回车转换行标志 CRNL 置位，则将字符转换为换行符 NL(10）【注意，此处只是转换字符，并没有写入secondary队列】;
			if (I_CRNL(tty))
				c=10;
			// 否则如果忽略回车标志 NOCR 置位，则忽略该字符，继续处理其他字符。
			else if (I_NOCR(tty))
				continue;
			else ;
		// 如果字符是换行符 NL（10），并且换行转回车标志 NLCR 置位，则将其转换为回车符 CR(13)
		else if (c==10 && I_NLCR(tty))
			c=13;
		// 如果大写转小写标志 UCLC 置位，则将该字符转换为小写字符
		if (I_UCLC(tty))
			c=tolower(c);
		/*
		如果本地模式标志集中的 规范模式 标志 CANON 已置位，则对读取的字符进行以下处理:
		*/
		if (L_CANON(tty)) {
			/*
			1、 如果该字符是键盘终止控制字符KILL（^U），则对己输入的当前行执行删除处理。删除一行字符的循环过程如下：
			*/
			if (c==KILL_CHAR(tty)) {
				/* deal with killing the input line */
				/*
				如果tty辅助队列不空，并且取出的辅助队列中最后一个字符不是换行符NL（10），并且该字符不是文件结束字符（^D），
				则循环执行下列代码，直到secondary队列中的当前行为空：
				*/
				while(!(EMPTY(tty->secondary) ||
				        (c=LAST(tty->secondary))==10 ||
				        c==EOF_CHAR(tty))) {
					/*
					如果本地回显标志 ECHO 置位，那么：
					*/
					if (L_ECHO(tty)) {
						// 若（辅助队列中最后一个字符）字符是控制字符（值<32），则往 tty 写队列write_q中放入擦除控制字符 ERASE（^H)。
						if (c<32)
							PUTCH(127,tty->write_q); // 控制字符要删2字节。补充：因为控制字符在放入写队列时需要用2个字节表示（例如^V），因此要求特别对控制字符多放入一个 ERASE。
						// 然后再放入一个擦除字符 ERASE，并且调用该 tty 写函数，把写队列中的所有字符输出到终端屏幕上。
						PUTCH(127,tty->write_q);
						tty->write(tty);  // 
					}
					// 最后将 tty辅助队列头指针后退1字节。
					DEC(tty->secondary.head);
				}
				// 继续处理read_q中的下一个字符
				continue;
			}
			/*
			2、如果该字符是删除控制字符 ERASE（^H），那么：
			*/
			if (c==ERASE_CHAR(tty)) {
				// 如果 tty 的辅助队列为空，或者其最后一个字符是换行符NL(10），或者是文件结束符，则继续处理下一个其他字符。
				if (EMPTY(tty->secondary) ||
				   (c=LAST(tty->secondary))==10 ||
				   c==EOF_CHAR(tty))
					continue;
				// 如果本地回显标志 ECHO 置位，那么：
				if (L_ECHO(tty)) {
					// 若字符是控制字符（值＜32），则往 tty 的写队列中放入擦除字符 ERASE。
					if (c<32)
						PUTCH(127,tty->write_q);
					// 再放入一个擦除字符 ERASE，并且调用该 tty 的写函数。
					PUTCH(127,tty->write_q);
					tty->write(tty);  // con_write() or  rs_write()
				}
				// 最后将 tty 辅助队列头指针后退1字节
				DEC(tty->secondary.head);
				// 继续处理下一个字符
				continue;
			}
			// 3、如果字符是停止控制字符（^S），则置 tty 停止标志，停止tty 输出，并继续处理其他字符。
			if (c==STOP_CHAR(tty)) {
				tty->stopped=1;
				// 继续处理下一个字符
				continue;
			}
			// 4、如果字符是开始控制字符（^Q），则复位 tty 停止标志，恢复 tty输出，并继续处理其他字符。
			if (c==START_CHAR(tty)) {
				tty->stopped=0;
				// 继续处理下一个字符
				continue;
			}
		}
		/*
		若输入模式标志集中 ISIG 标志置位，表示终端键盘可以产生信号，
		则在收到控制字符 INTR、 QUIT、SUSP 或 DSUSP 时，需要为进程产生相应的信号。
		*/
		if (L_ISIG(tty)) {
			// 如果该字符是键盘中断符（^C）， 则向当前进程之进程组中所有进程发送键盘终止信号，并继续处理下一字符。
			if (c==INTR_CHAR(tty)) {
				tty_intr(tty,INTMASK);
				continue;
			}
			// 如果该字符是退出符（^\），则向当前进程之进程组中所有进程发送键盘退出信号，并继续处理下一字符。
			if (c==QUIT_CHAR(tty)) {
				tty_intr(tty,QUITMASK);
				continue;
			}
		}
		/*
		如果该字符是换行符NL（10），或者是文件结束符 EOF（4，^D），表示一行字符己处理完，
		则把辅助缓冲队列中当前含有字符行数值 secondary.data 增1（secondary中计数以行为单位）。
		说明：如果在函数 tty_read()中取走一行字符，该值即会被减1。
		*/
		if (c==10 || c==EOF_CHAR(tty))
			tty->secondary.data++;
		/*
		如果本地模式标志集中回显标志 ECHO 在置位状态，那么，
		*/
		if (L_ECHO(tty)) {
			// 如果字符是换行符NL（10），则将换行符NL（10）和回车符 CR(13）放入 tty 写队列缓冲区中；
			if (c==10) {
				PUTCH(10,tty->write_q);
				PUTCH(13,tty->write_q);
			// 如果字符是控制字符（值<32) 并且回显控制字符标志 ECHOCTL 置位，则将字符’^’和字符 c+64 放入 tty 写队列中（也即会显示^C、^H等)；
			} else if (c<32) {
				if (L_ECHOCTL(tty)) {
					PUTCH('^',tty->write_q);
					PUTCH(c+64,tty->write_q);
				}
			// 否则将该字符直接放入 tty 写缓冲队列中。最后调用该 tty 写操作函数。
			} else
				PUTCH(c,tty->write_q);
			tty->write(tty);
		}
		// 每一次循环末，将处理过的字符放入辅助队列中
		PUTCH(c,tty->secondary);
	}
	// 最后，在退出循环体后，唤醒等待该辅助缓冲队列的进程（如果有的话）
	wake_up(&tty->secondary.proc_list);
}

// tty 读函数
// 从终端辅助缓冲队列中读取指定数量的字符，放到用户指定的缓冲区中。
// 参数：channel - 子设备号；buf - 用户缓冲区指针；nr - 欲读字节数。
// 返回己读字节数。
int tty_read(unsigned channel, char * buf, int nr)
{
	struct tty_struct * tty;
	char c, * b=buf;
	int minimum,time,flag=0;
	long oldalarm;

	if (channel>2 || nr<0) return -1;
	tty = &tty_table[channel];
	oldalarm = current->alarm;
	time = 10L*tty->termios.c_cc[VTIME];  	// 读字符操作超时定时值，单位：滴答 （VTIME 是一个 1/10 秒计数计时值（10个滴答），此处转换为滴答数）
	minimum = tty->termios.c_cc[VMIN];		// 最少需要读取的字符个数
	// 进行一些参数设置
	if (time && !minimum) {
		// 没有设置最少读取个数 minimum，即minimum=0，则修改为1
		minimum=1;
		// 如果进程原定时值是0，或者 time 加上当前系统时间值小于进程原定时值的话，
		// 则置重新设置进程定时值为（time +当前系统时间》，并置 flag 标志。
		if ((flag=(!oldalarm || time+jiffies<oldalarm)))
			current->alarm = time+jiffies;
	}
	// 如果这里设置的最少读取字符数大于欲读取的字符数，则令其等于此次欲读取的字符数nr。
	if (minimum>nr)
		minimum=nr;
	
	/*
	现在我们开始从辅助队列中循环取出字符并放到用户缓冲区 buf 中。
	当欲读的字节数大于 0， 则执行以下循环操作。
	*/
	while (nr>0) {
		// 读取超时，中断循环读取
		// 如果 flag 已设置（即进程原定时值是0，或者 time + 当前系统时间值小于进程原定时值），
		// 并且进程此时己收到定时报警信号 SIGALRM，表明这里新设置的定时时间己到（而不是原定时时间已到）。
		if (flag && (current->signal & ALRMMASK)) {
			// 复位进程的定时信号 SIGALRM，并中断循环
			current->signal &= ~ALRMMASK;
			break;
		}
		// 如果 flag 没有置位，即是收到了原定时的报警信号；或者 flag 已置位但当前进程此时收到了其他信号，
		// 则退出循环。
		if (current->signal)
			break;
		
		// 如果辅助缓冲队列为空，或者设置了规范模式标志，并且辅助队列中字符行数为0以及辅助模式缓冲队列空闲空间>20（字符数超多的行），
		// 则让当前进程进入可中断睡眠状态，返回后继续处理。
		/*
			1、如果辅助缓冲队列为空。则让当前进程进入可中断睡眠状态，返回后继续处理
			2、设置了规范模式标志，并且辅助队列中字符行数为0以及辅助模式缓冲队列空闲空间>20（字符数超多的行），
			则不执行读取操作，continue重新执行循环，进行等待直到secondary中有完整的一行(secondary.data>0)。
			(当空闲空间小于20时，则不再等待，直接进行读取操作)
			3、如果以上二者都不满足，则进行下面的读取字符操作
		*/

		// 由于规范模式时，内核以行为单位为用户提供数据，因此在该模式下辅助队列中必须起码有一行字符可供取用，即 secondary.data 起码是1才行。
		// 另外，由这里的 LEFT() 判断可知，即使辅助队列中还没有放完一行（即应该有一个回车符），但是如果此时一行字符个数己经超过 1024-20= 1004个字符，那么内核也会立刻执行读取操作。
		if (EMPTY(tty->secondary) || (L_CANON(tty) &&
		!tty->secondary.data && LEFT(tty->secondary)>20)) {
			sleep_if_empty(&tty->secondary);
			continue;
		}

		// 执行读取操作
		/*
		下面开始正式执行取字符操作。需读字符数 nr 依次递减，直到 nr=0 或者辅助缓冲队列为空。
		*/
		do {
			// 取辅助缓冲队列字符 c，并移动secondary.tail指针
			GETCH(tty->secondary,c);
			// 如果所取字符是文件结束符（^D）或者是换行符NL（10），则把辅助缓冲队列中含有字符行数值减1。
			if (c==EOF_CHAR(tty) || c==10)
				tty->secondary.data--;
			// 如果该字符是文件结束符（^D）并且规范模式标志成置位状态，此时文件结束符表示文件结束，则返回己读字符数，并退出。
			if (c==EOF_CHAR(tty) && L_CANON(tty))
				return (b-buf);
			// 否则说明现在还没有遇到文件结束符，或者正处于原始（非规范）模式。
			// 在这种模式中，用户以字符流作为读取对象，也不识别其中的控制字符（如文件结束符）。
			// 于是将字符直接放入用户数据缓冲区 buf 中，并把欲读字符数减1。
			// 此时如果欲读字符数已为0，则中断循环。否则，只要还没有取完欲读字符数并且辅助队列不空，就继续取队列中的字符。
			else {
				put_fs_byte(c,b++);
				if (!--nr)
					break;
			}
		} while (nr>0 && !EMPTY(tty->secondary));

		/*
		对读取终止进行处理：
		执行到此说明我们已经读取了 nr 个字符，或者辅助队列己经被取空了。
		对于已经读取nr 个字符的情况，我们无需做什么，程序会自动退出while循环，恢复进程原定时值并作退出处理。

		如果由于辅助队列中字符被取空而退出上面 do-while 循环，
		那么我们就需要根据终端 termios 控制字符数组的 time 设置的延时时间来确定是否要等待一段时间。
		*/
		// 如果超时定时值 time 不为0，并且规范模式标志没有置位(非规范模式），那么：
		if (time && !L_CANON(tty)) {
			// 如果进程原定时值是0或者 time+ 当前系统时间 值小于进程原定时值 oldalarm，则置重新设置进程定时值为 time+当前系统时间，并置 flag 标志，为还需要读取的字符做好定时准备。
			if ((flag=(!oldalarm || time+jiffies<oldalarm)))
				current->alarm = time+jiffies;
			// 否则表明进程原定时时间要比等待字符被读取的定时时间要早，可能没有等到字符到来进程原定时时间就到了。
			// 因此，此时这里需要恢复进程的原定时值 oldalarm。
			else
				current->alarm = oldalarm;
		}

		// 另外，如果设置了规范模式标志，那么若己读到起码一个字符，则中断循环。
		// 否则，若己读取数≥最少要求读取的字符数，则也中断循环。
		if (L_CANON(tty)) {
			if (b-buf)
				break;
		} else if (b-buf >= minimum)
			break;
	}
	// 此时读取 tty 字符循环操作结束，因此让进程的定时值恢复原值。
	// 最后，如果进程接收到信号并且没有读取到任何字符，则返回出错号（被中断）。否则返回己读字符数。
	current->alarm = oldalarm;
	if (current->signal && !(b-buf))
		return -EINTR;
	return (b-buf);
}

// tty 写函数。
// 把用户缓冲区中的字符写入tty 写队列缓冲区中。
// 参数：channel-子设备号；buf 一缓冲区指针；nr-写字节数。
// 返回己写字节数。
int tty_write(unsigned channel, char * buf, int nr)
{
	static int cr_flag=0;
	struct tty_struct * tty;
	char c, *b=buf;

	if (channel>2 || nr<0) return -1;
	tty = channel + tty_table;
	// 字符设备是一个一个字符进行处理的，所以这里对于 nr 大于0时对每个字符进行循环处理。
	while (nr>0) {
		// 如果此时 tty 写队列已满，则当前进程进入可中断的睡眼状态。
		sleep_if_full(&tty->write_q);
		// 如果当前进程有信号要处理，则退出循环体。
		if (current->signal)
			break;
		
		// 当要写的字节数 nr 还大于0并且 tty 写队列不满，则循环执行以下操作。
		while (nr>0 && !FULL(tty->write_q)) {
			// 从用户数据缓冲区中取1字节c
			c=get_fs_byte(b);
			// 如果终端输出模式标志集中的执行输出处理标志 OPOST 置位。则执行对字符的后处理操作。
			if (O_POST(tty)) {
				if (c=='\r' && O_CRNL(tty))  // 回车转换行
					c='\n';
				else if (c=='\n' && O_NLRET(tty))   // 换行转回车
					c='\r';
				/*
				如果该字符是换行符’\n’并且回车标志 cr_flag 没有置位，但换行转换行转回车标志 ONLCR 置位的话，
				则将 cr_flag 标志置位，并将一回车符放入写队列中。
				然后继续处理下一个字符。
				*/
				if (c=='\n' && !cr_flag && O_NLCR(tty)) {
					cr_flag = 1; // 表示write_q中当前最新写入的字符是回车符
					PUTCH(13,tty->write_q);
					continue;
				}
				// 如果小写转大写标志 OLCUC 置位的话，就将该字符转成大写字符。
				if (O_LCUC(tty))
					c=toupper(c);
			}
			// 接着把用户数据缓冲指针b 前移1字节；欲写字节数减1字节；
			// 复位 cr_flag 标志，并将该字节放入 tty 写队列中。
			b++; nr--;
			cr_flag = 0;
			PUTCH(c,tty->write_q);
		}
		// 若要求的字符全部写完，或者写队列己满，则程序退出循环。
		// 此时会调用对应 tty 写函数， 把写队列缓冲区中的字符显示在控制台屏幕上，或者通过串行端口发送出去。
		tty->write(tty);  // con_write() or  rs_write()
		// 若还有字节要写，则等待写队列中字符取走。所以这里调用调度程序，先去执行其他任务。
		if (nr>0)
			schedule();
	}
	return (b-buf); // 最后返回写入的字节数。
}

/*
 * Jeh, sometimes I really like the 386.
 * This routine is called from an interrupt,
 * and there should be absolutely no problem
 * with sleeping even in an interrupt (I hope).
 * Of course, if somebody proves me wrong, I'll
 * hate intel for all time :-). We'll have to
 * be careful and see to reinstating the interrupt
 * chips before calling this, though.
 *
 * I don't think we sleep here under normal circumstances
 * anyway, which is good, as the task sleeping might be
 * totally innocent.
 */
void do_tty_interrupt(int tty)
{
	copy_to_cooked(tty_table+tty);
}

void chr_dev_init(void)
{
}
